{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e715bfc4-b35c-4387-9ef4-4907d4ee343b",
   "metadata": {},
   "outputs": [],
   "source": [
    "pip pinecone"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "232700da-f47a-4638-bd51-2796893b9b6c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pinecone import Pinecone, ServerlessSpec\n",
    "\n",
    "pinecone = Pinecone(api_key=\"pcsk_TCZLb_n7yFoy1mV8YzXEuw4kTbQQ1jp9vAuKozVAP5C71xgekAKUd4fLAMSBaxEDaRoi\")\n",
    "# 索引名称\n",
    "index_name = \"mnist-index\"\n",
    "\n",
    "################### 检查、清除之前创建的索引 ###################\n",
    "# 获取现有索引列表\n",
    "existing_indexes = pinecone.list_indexes()\n",
    "\n",
    "# 检查索引是否存在，如果存在就删除\n",
    "# 这个if是否需要，要看情况而定\n",
    "# 比如有的时候，如果不想要重复删除在创建，这个if就可以不要\n",
    "if any(index['name'] == index_name for index in existing_indexes):\n",
    "    print(f\"索引 '{index_name}' 已存在，正在删除...\")\n",
    "    pinecone.delete_index(index_name)\n",
    "    print(f\"索引 '{index_name}' 已成功删除。\")\n",
    "else:\n",
    "    print(f\"索引 '{index_name}' 不存在，将创建新索引。\")\n",
    "################### 检查、清除之前创建的索引 ###################\n",
    "\n",
    "index_name = \"mnist-index\" # 索引名称\n",
    "\n",
    "# 创建新索引\n",
    "print(f\"正在创建新索引 '{index_name}'...\")\n",
    "pinecone.create_index(\n",
    "    name=index_name,\n",
    "    dimension=64,  # MNIST 每个图像展平后是一个 64 维向量\n",
    "    metric=\"euclidean\",  # 使用欧氏距离\n",
    "    spec=ServerlessSpec(\n",
    "        cloud=\"aws\",\n",
    "        region=\"us-east-1\"\n",
    "    )\n",
    ")\n",
    "print(f\"索引 '{index_name}' 创建成功。\")\n",
    "\n",
    "# 连接到索引\n",
    "index = pinecone.Index(index_name)\n",
    "print(f\"已成功连接到索引 '{index_name}'。\")\n",
    "# 从 scikit-learn 库中导入 load_digits 函数\n",
    "# 这个函数用于加载著名的手写数字数据集 MNIST\n",
    "from sklearn.datasets import load_digits\n",
    "\n",
    "# 使用 load_digits 函数加载 MNIST 数据集\n",
    "# n_class=10 表示加载全部 10 个数字类别(0-9)\n",
    "digits = load_digits(n_class=10)\n",
    "\n",
    "# 获取数据集中的特征数据\n",
    "# X 是一个二维数组,每行代表一个样本,每个样本是一个 64 维的向量(8x8 像素展平)\n",
    "X = digits.data\n",
    "\n",
    "# 获取数据集中的标签\n",
    "# y 是一个一维数组,包含每个样本对应的真实数字标签(0-9)\n",
    "y = digits.target\n",
    "\n",
    "# 初始化一个空列表,用于存储转换后的向量数据\n",
    "vectors = []\n",
    "\n",
    "# 遍历所有样本,将数据转换为 Pinecone 可接受的格式\n",
    "for i in range(len(X)):\n",
    "    # 使用样本的索引作为向量的唯一标识符\n",
    "    vector_id = str(i)\n",
    "    \n",
    "    # 将 NumPy 数组转换为 Python 列表\n",
    "    # Pinecone 要求输入数据为 Python 列表格式\n",
    "    vector_values = X[i].tolist()\n",
    "    \n",
    "    # 创建元数据字典,包含该样本的真实标签\n",
    "    # 将标签转换为整数类型,确保数据类型的一致性\n",
    "    metadata = {\"label\": int(y[i])}\n",
    "    \n",
    "    # 将转换后的数据(ID、向量值、元数据)作为元组添加到 vectors 列表中\n",
    "    vectors.append((vector_id, vector_values, metadata))\n",
    "\n",
    "# 定义批处理大小,每批最多包含 1000 个向量\n",
    "# 这是为了避免一次性向 Pinecone 发送过多数据,可能导致请求超时或失败\n",
    "batch_size = 1000\n",
    "\n",
    "# 使用步长为 batch_size 的 range 函数,实现分批处理\n",
    "for i in range(0, len(vectors), batch_size):\n",
    "    # 从 vectors 列表中切片获取一批数据\n",
    "    batch = vectors[i:i + batch_size]\n",
    "    \n",
    "    # 使用 upsert 方法将这批数据上传到 Pinecone 索引中\n",
    "    # upsert 操作会插入新的向量或更新已存在的向量\n",
    "    index.upsert(batch)\n",
    "# 导入必要的库\n",
    "import matplotlib.pyplot as plt  # 用于绘图\n",
    "import numpy as np  # 用于数值计算\n",
    "from collections import Counter  # 用于计数\n",
    "\n",
    "# 创建一个手写数字 3 的图像\n",
    "# 使用 numpy 数组表示一个 8x8 的二维图像\n",
    "# 255 表示白色像素,0 表示黑色像素\n",
    "digit_3 = np.array(\n",
    "    [[0, 0, 255, 255, 255, 255, 0, 0],\n",
    "     [0, 0, 0, 0, 0, 255, 0, 0],\n",
    "     [0, 0, 0, 0, 0, 255, 0, 0],\n",
    "     [0, 0, 0, 255, 255, 255, 0, 0],\n",
    "     [0, 0, 0, 0, 0, 255, 0, 0],\n",
    "     [0, 0, 0, 0, 0, 255, 0, 0],\n",
    "     [0, 0, 0, 0, 0, 255, 0, 0],\n",
    "     [0, 0, 255, 255, 255, 255, 0, 0]]\n",
    ")\n",
    "\n",
    "# 将图像像素值从 0-255 的范围缩放到 0-16 的范围\n",
    "# 这是为了匹配 MNIST 数据集中使用的像素值范围\n",
    "digit_3_flatten = (digit_3 / 255.0) * 16\n",
    "\n",
    "# 将二维图像数组展平成一维列表\n",
    "# 这是因为 Pinecone 要求输入向量是一维的\n",
    "query_data = digit_3_flatten.ravel().tolist()\n",
    "\n",
    "# 使用准备好的查询向量在 Pinecone 索引中执行搜索\n",
    "results = index.query(\n",
    "    vector=query_data,\n",
    "    top_k=11,  # 返回距离最近的 11 个结果\n",
    "    include_metadata=True  # 同时返回每个向量的元数据(包括标签)\n",
    ")\n",
    "\n",
    "# 从搜索结果中提取每个匹配项的标签\n",
    "labels = [match['metadata']['label'] for match in results['matches']]\n",
    "\n",
    "# 打印每个匹配结果的详细信息\n",
    "for match, label in zip(results['matches'], labels):\n",
    "    print(f\"id: {match['id']}, distance: {match['score']}, label: {label}\")\n",
    "\n",
    "# 使用投票机制确定最终的分类结果\n",
    "# Counter().most_common(1) 返回出现次数最多的元素\n",
    "# [0][0] 获取该元素的值(即预测的数字)\n",
    "final_prediction = Counter(labels).most_common(1)[0][0]\n",
    "\n",
    "# 使用 matplotlib 显示查询图像和预测结果\n",
    "plt.imshow(digit_3, cmap='gray')  # 显示灰度图像\n",
    "plt.title(f\"Predicted digit: {final_prediction}\", size=15)  # 设置标题,显示预测结果\n",
    "plt.axis('off')  # 关闭坐标轴\n",
    "plt.show()  # 展示图像"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "edge-kernel",
   "language": "python",
   "name": "edge-kernel"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
